package com.fitso.model.util.common;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.collections.list.SetUniqueList;
import org.apache.commons.lang.StringUtils;

/**
 * <p>
 * Utility class for doing some simple reflection tasks on classes and beans
 * </p>
 * 
 * @author timothystorm
 */
public class ReflectionUtils extends org.springframework.util.ReflectionUtils {
	/**
	 * Matches method criteria
	 * 
	 * @author timothystorm
	 */
	private interface MethodMatchClosure {
		public boolean methodMatch(Method m);
	}

	private static class ReadMethodClosure implements MethodMatchClosure {
		public boolean methodMatch(Method m) {
			return !Void.TYPE.equals(m.getReturnType());
		}
	}

	private static class WriteMethodClosure implements MethodMatchClosure {
		public boolean methodMatch(Method m) {
			return Void.TYPE.equals(m.getReturnType());
		}
	}

	private static class GetterMethodClosure extends ReadMethodClosure {
		public boolean methodMatch(Method m) {
			if (super.methodMatch(m)) {
				return StringUtils.startsWith(m.getName(), "get") || StringUtils.startsWith(m.getName(), "is");
			}
			return false;
		}
	}

	private static class SetterMethodClosure extends WriteMethodClosure {
		public boolean methodMatch(Method m) {
			if (super.methodMatch(m)) {
				return StringUtils.startsWith(m.getName(), "set") || StringUtils.startsWith(m.getName(), "is");
			}
			return false;
		}
	}

	private static Method[] _objectMethods;

	private static boolean contains(Method[] methods, Method method) {
		for (Method m : methods) {
			if (m.getName().equals(method.getName())) {
				return true;
			}
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	public static Method[] getAllBeanMethods(Class<?> clazz) {
		if (_objectMethods == null) {
			Method[] excludeMethods = ReflectionUtils.getAllDeclaredMethods(Object.class);
			_objectMethods = new Method[excludeMethods.length];

			for (int i = 0; i < excludeMethods.length; i++) {
				_objectMethods[i] = excludeMethods[i];
			}
		}

		List<Method> methods = SetUniqueList.decorate(new ArrayList<Method>());
		Class<?> c = clazz;

		do {
			Method[] classMethods = c.getMethods();

			for (Method m : classMethods) {
				if (!contains(_objectMethods, m)) {
					methods.add(m);
				}
			}
			c = c.getSuperclass();
		}
		while (c != Object.class);

		return methods.toArray(new Method[methods.size()]);
	}

	@SuppressWarnings("unchecked")
	private static Method[] getMethods(Class<?> clazz, MethodMatchClosure is) {
		Method[] classMethods = getAllBeanMethods(clazz);
		List<Method> methods = SetUniqueList.decorate(new ArrayList<Method>());

		for (Method m : classMethods) {
			if (is.methodMatch(m)) {
				methods.add(m);
			}
		}
		return methods.toArray(new Method[methods.size()]);
	}

	/**
	 * Returns all attributes that have a get/set/is method. It does not return
	 * the actual attributes since those are probably private. It is assumed
	 * that the get/set/is methods mask the attribute name.
	 * 
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static String[] getBeanAttributes(Class<?> clazz) {
		Method[] methods = getAllBeanMethods(clazz);
		List<String> names = SetUniqueList.decorate(new ArrayList<String>());

		String name;
		for (Method m : methods) {
			name = m.getName();
			name = StringUtils.removeStart(name, "get");
			name = StringUtils.removeStart(name, "set");
			name = StringUtils.removeStart(name, "is");
			name = Character.toLowerCase(name.charAt(0)) + name.substring(1);

			names.add(name);
		}
		return names.toArray(new String[names.size()]);
	}

	public static Method[] getReadMethods(Class<?> clazz) {
		return getMethods(clazz, new ReadMethodClosure());
	}

	public static Method[] getWriteMethods(Class<?> clazz) {
		return getMethods(clazz, new WriteMethodClosure());
	}

	public static Method[] getBeanGetters(Class<?> clazz) {
		return getMethods(clazz, new GetterMethodClosure());
	}

	public static Method[] getBeanGetters(Class<?> clazz, Class<?> returnType) {
		Method[] getters = getMethods(clazz, new GetterMethodClosure());

		List<Method> returnTypeGetters = new ArrayList<Method>();
		for (Method m : getters) {
			if (m.getReturnType().equals(returnType)) {
				returnTypeGetters.add(m);
			}
		}
		return returnTypeGetters.toArray(new Method[returnTypeGetters.size()]);
	}

	public static Method[] getBeanSetters(Class<?> clazz) {
		return getMethods(clazz, new SetterMethodClosure());
	}

	public static Object instance(Class<?> clazz) {
		Object target = null;

		try {
			target = Class.forName(clazz.getName()).newInstance();
		}
		catch (ClassNotFoundException ex) {
			System.err.println(ex + " Interpreter class must be in class path.");
		}
		catch (InstantiationException ex) {
			System.err.println(ex + " Interpreter class must be concrete.");
		}
		catch (IllegalAccessException ex) {
			System.err.println(ex + " Interpreter class must have a no-arg constructor.");
		}
		return target;
	}

	public static void invokeMethod(Object type, Method method, Object... values) throws IllegalArgumentException,
			IllegalAccessException, InvocationTargetException {
		ReflectionUtils.makeAccessible(method);
		method.invoke(type, values);
	}
}
